package com.dgut.uhrs.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

@Configuration
public class DgutCasConfig<H extends HttpSecurityBuilder<H>> extends
        AbstractHttpConfigurer<DgutCasConfig<H>, H> {
    /**
     * 自定义安全过滤链
     */
    @Override
    public void configure(H http) {
        http.addFilterAfter(new DgutCasOauth2RedirectFilter(), LogoutFilter.class);
        http.addFilterAfter(new DgutCasOAuth2LoginAuthenticationFilter(),LogoutFilter.class);
    }

    /**
     * 莞工中央认证重定向过滤器
     */
    static class DgutCasOauth2RedirectFilter extends OncePerRequestFilter {
        private static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/dgut";
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                        FilterChain filterChain) throws ServletException, IOException {
            // 如果不是/oauth2/dgut请求，则继续下一个过滤器。
            if (!request.getRequestURI().endsWith(DEFAULT_AUTHORIZATION_REQUEST_BASE_URI)) {
                filterChain.doFilter(request, response);
                return;// 执行完安全过滤器链后不执行后面的代码。
            }
            String uriString = UriComponentsBuilder.fromUriString("https://cas.dgut.edu.cn?appid=javaee")
                    .encode()                  //将特殊字符转码
                    .toUriString();
            response.sendRedirect(uriString);    //重定向到uriString
        }
    }

    /**
     * 莞工中央认证OAuth2认证过滤器
     */
    static class DgutCasOAuth2LoginAuthenticationFilter extends OncePerRequestFilter {
        private static final String DEFAULT_CALLBACK_BASE_URI = "/login/dgut";
        private static final String DEFAULT_LOGIN_SUCCESS_REDIRECT_URL = "/adminLoginWithUserId";
        private static final ProviderManager providerManager = new ProviderManager(Collections.singletonList(new DgutOAuth2AuthenticationProvider()));
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String token = request.getParameter("token");
            if (!request.getRequestURI().endsWith(DEFAULT_CALLBACK_BASE_URI) || !StringUtils.hasText(token)) {
                filterChain.doFilter(request, response);
                return;
            }
            // Spring Security基本校验流程
            DgutOAuth2LoginAuthenticationToken dgutOAuth2LoginAuthenticationToken = new DgutOAuth2LoginAuthenticationToken(token, request);
            Authentication successAuthentication;
            try {
                successAuthentication = providerManager.authenticate(dgutOAuth2LoginAuthenticationToken);
            } catch (AuthenticationException e) {
                // 如果校验失败providerManager会抛异常，在catch里作异常处理。
                return;
            }
            // 校验成功，执行后面的流程：
            SecurityContextHolder.getContext().setAuthentication(successAuthentication);
            // 改变 session id (只是改id,没有删除session)
//            request.changeSessionId();
            // 移除之前认证时的错误信息
            request.getSession().removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
            // 成功登录后，设置重定向到页面。
            redirectStrategy(request,response);
        }

        /**
         * 决定 Gitee 登录成功的重定向策略
         * 每条安全过滤链都默认添加了配置器{@link RequestCacheConfigurer} ，它会在安全过滤中添加了一个过滤器{@link RequestCacheAwareFilter}，
         * 它会缓存进入安全过滤链的HttpServletRequest对象，request实际是保存在当前session的SPRING_SECURITY_SAVED_REQUEST属性中的,详细查看{@link HttpSessionRequestCache}。
         * 当过滤链捕获到认证异常或访问拒绝异常时，ExceptionTranslationFilter就会缓存这次的HttpServletRequest对象，查看{@code ExceptionTranslationFilter#sendStartAuthentication}
         * {@link DefaultRedirectStrategy}工具类可以很方便帮助我们构造重定向的http响应。
         * Spring Security框架的默认的认证成功后的重定向逻辑由{@link SavedRequestAwareAuthenticationSuccessHandler}处理，
         * 本方法是根据它的逻辑写的，为了尽量与Spring Security兼容。
         */
        private void redirectStrategy(HttpServletRequest request, HttpServletResponse response) throws IOException {
            String redirectUrl = DEFAULT_LOGIN_SUCCESS_REDIRECT_URL;
            SavedRequest savedRequest = (SavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
            if (Objects.nonNull(savedRequest))
                redirectUrl = savedRequest.getRedirectUrl();
            new DefaultRedirectStrategy().sendRedirect(request, response, redirectUrl);
        }
    }

    /**
     * 自定义的AuthenticationProvider
     * 通过token获取access_token和openid,再由access_token和openid拉取莞工中央认证授权用户的信息
     */
    static class DgutOAuth2AuthenticationProvider implements AuthenticationProvider {
        private static final RestTemplate rest = new RestTemplate();
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            // 用户认证前，构造一个DgutOAuth2LoginAuthenticationToken
            DgutOAuth2LoginAuthenticationToken authenticationToken = (DgutOAuth2LoginAuthenticationToken) authentication;
            // 通过API获取access_token
            List<String> strings = getAccessToken(authenticationToken.getToken());
            Map<String, Object> userInfo = getUserInfo(strings.get(0),strings.get(1));
            // 认证成功后，重新生成Authentication
            return createSuccessAuthentication(Objects.requireNonNull(userInfo), authenticationToken.getRequest());
        }
        /**
         * 获取莞工API的访问令牌access_token和openid
         */
        private List<String> getAccessToken(String token) {
            //验证登录信息
            String appid = "javaee";
            String appsecret = "b3b52e43ccfd";
            String userip = "10.0.2.2";
            //步骤三：使用莞工中央认证token API向莞工中央认证认证服务器发送post请求获取access_token和openid。
            String uriString = UriComponentsBuilder
                    .fromUriString("https://cas.dgut.edu.cn/ssoapi/v2/checkToken?token={token}&appid={appid}&appsecret={appsecret}&userip={userip}")
                    .buildAndExpand(token,appid,appsecret,userip).toUriString();
            RequestEntity<Void> requestEntity = RequestEntity.post(uriString)
                    .headers(httpHeaders -> httpHeaders.add("User-Agent",
                            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"))
                    .build();
            ResponseEntity<String> exchange = rest.exchange(requestEntity, String.class);
            String body = exchange.getBody();
            Map<String, Object> result = new JacksonJsonParser(new ObjectMapper()).parseMap(body);
            String access_token = result.get("access_token").toString();
            String openid = result.get("openid").toString();
            List<String> strings = new ArrayList<>();
            strings.add(access_token);
            strings.add(openid);
            return strings;
        }

        /**
         * 获取莞工中央认证授权用户的信息
         */
        private Map<String, Object> getUserInfo(String access_token,String openid) {
            /// 步骤四：使用莞工API获取授权用户的资料。
            String uriString = UriComponentsBuilder.fromUriString("https://cas.dgut.edu.cn/oauth/getUserInfo?access_token={access_token}&openid={openid}")
                    .buildAndExpand(access_token,openid).toUriString();
            RequestEntity<Void> requestEntity = RequestEntity.post(uriString)
                    .build();
            ResponseEntity<String> responseEntity = rest.exchange(requestEntity, String.class);
            String body = responseEntity.getBody();
            Map<String, Object> result = new JacksonJsonParser(new ObjectMapper()).parseMap(body);
            return result;
        }

        /**
         * 认证成功后，重新构造Authentication。
         */
        private Authentication createSuccessAuthentication(Map<String, Object> userInfo, HttpServletRequest request) {
            // 构造 UserDetails
            // 可以实现自己的 UserDetails 以便提供更详细的用户信息。
            User user = new User(userInfo.get("username").toString(), "",
                    AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
            DgutOAuth2LoginAuthenticationToken authenticationToken =
                    new DgutOAuth2LoginAuthenticationToken(user, AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
            // 设置认证用户的额外信息，比如 IP 地址、经纬度等。
            // 下面代码将赋值一个WebAuthenticationDetails对象，它的构造函数是request,会封装HttpServletRequest的信息。
            AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
                    new WebAuthenticationDetailsSource();
            authenticationToken.setDetails(authenticationDetailsSource.buildDetails(request));
            return authenticationToken;
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return DgutOAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);
        }
    }

    /**
     * 自定义的Authentication
     * <p></p>
     * 校验前只包含token与reuqest;
     * <p>
     * 校验成功后包含principal、authorities.
     */
    static class DgutOAuth2LoginAuthenticationToken extends AbstractAuthenticationToken {

        private String token;
        private Object principal;
        private HttpServletRequest request;

        public DgutOAuth2LoginAuthenticationToken(String token, HttpServletRequest request) {
            super(Collections.emptyList());
            this.token = token;
            this.request = request;
        }

        public DgutOAuth2LoginAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
            super(authorities);
            this.principal = principal;
            setAuthenticated(true);
        }

        public String getToken() {
            return token;
        }

        public HttpServletRequest getRequest() {
            return request;
        }

        @Override
        public Object getCredentials() {
            return "";
        }

        @Override
        public Object getPrincipal() {
            return this.principal;
        }
    }
}
